/*******************************************************************************
 * Copyright 2010 Prometheus Consulting
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package nz.co.senanque.validationengine;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import nz.co.senanque.validationengine.metadata.ClassMetadata;
import nz.co.senanque.validationengine.metadata.PropertyMetadata;
import nz.co.senanque.validationengine.metadata.PropertyMetadataImpl;

/**
 * 
 * This class is responsible for binding the graphed objects into the validator
 * session The arrays being used must be converted to listener arrays. 
 * 
 * @author Roger Parkinson
 * @version $Revision: 1.11 $
 */
public class Binder
{
    private final transient ValidationEngine m_validationEngine;

    public Binder(final ValidationEngine validationEngine)
    {
        m_validationEngine = validationEngine;
    }

    public Map<ValidationObject, ProxyObject> bind(final ValidationObject object,
            final ValidationSession session)
    {
        return bind(object, session, null, null);
    }

    public Map<ValidationObject, ProxyObject> bind(final ValidationObject object,
            final ValidationSession session, final ProxyField parent, final Integer index)
    {
        final Map<ValidationObject, ProxyObject> ret = new IdentityHashMap<ValidationObject, ProxyObject>();
        if (object == null)
        {
            return ret;
        }
        final ClassMetadata classMetadata = m_validationEngine
                .getClassMetadata(object.getClass());
        if (classMetadata == null)
        {
            throw new RuntimeException("Class not recognised");
        }
        object.setValidationSession(session);
        final ProxyObject proxyObject = new ProxyObject(object, parent, index);
        ret.put(object, proxyObject);
        final ObjectMetadata objectMetadata = object.getMetadata();
        objectMetadata.setClassMetadata(proxyObject, classMetadata);
        for (Method method : object.getClass().getMethods())
        {
            if (method.getName().startsWith("get"))
            {
                final String fieldName = ValidationUtils.getFieldNameWithJavaCase(method.getName().substring(3));
                final PropertyMetadataImpl fieldMetadata = classMetadata
                        .getField(fieldName);
                if (fieldMetadata == null)
                {
                    continue;
                }
                final ProxyField proxyField = new ProxyField(fieldName,
                        proxyObject, parent, fieldMetadata);
                proxyObject.put(fieldName, proxyField);
                if (method.getReturnType().isAssignableFrom(List.class))
                {
                    try
                    {
                        final List<ValidationObject> validationObjects = (List<ValidationObject>) method.invoke(object,new Object[]{});
                        final ListeningArray<Object> array = new ListeningArray<Object>();
                        array.addAll(validationObjects);
                        array.setValidationSession(session);
                        array.setProxyField(proxyField);
                        final Method setterMethod = object.getClass().getMethod(ValidationUtils.figureSetter(fieldName), List.class);
                        setterMethod.invoke(object, array);
                        int index1 = 0;
                        for (ValidationObject child : validationObjects)
                        {
                            ret.putAll(bind(child, session, proxyField,index1++));
                        }
                    }
                    catch (Exception e)
                    {
                        throw new RuntimeException(e);
                    }
                    continue;
                }
                if (m_validationEngine.getClassMetadata(method.getReturnType()) != null)
                {
                    try
                    {
                        final ValidationObject child = (ValidationObject)method.invoke(object, new Object[]{});
                        ret.putAll(bind(child, session, proxyField,null));
                    }
                    catch (Exception e)
                    {
                        throw new RuntimeException(e);
                    }
                    continue;
                }
            }
        }
        return ret;
    }
    public void unbind(final ValidationObject object, final Map<ValidationObject, ProxyObject> boundMap)
    {
        if (object == null)
        {
            return;
        }

        final ProxyObject proxyObject = boundMap.remove(object);
        if (proxyObject == null)
        {
            for (ValidationObject item: boundMap.keySet())
            {
                if (item.equals(object))
                {
                    ((HashMap)boundMap).remove(object);
                }
            }
        }
        for (Method method : object.getClass().getMethods())
        {
            if (method.getName().startsWith("get"))
            {
                final String fieldName = ValidationUtils.getFieldNameWithJavaCase(method.getName().substring(3));
                final ClassMetadata classMetadata = m_validationEngine.getClassMetadata(object.getClass());
                final PropertyMetadata fieldMetadata = classMetadata.getField(fieldName);
                if (fieldMetadata == null)
                {
                    continue;
                }
                if (method.getReturnType().isAssignableFrom(List.class))
                {
                    try
                    {
                        final List<ValidationObject> validationObjects = (List<ValidationObject>) method.invoke(object,new Object[]{});
                        for (ValidationObject child : validationObjects)
                        {
                            unbind(child,boundMap);
                        }
                    }
                    catch (Exception e)
                    {
                        throw new RuntimeException(e);
                    }
                    continue;
                }
                if (m_validationEngine.getClassMetadata(method.getReturnType()) != null)
                {
                    try
                    {
                        ValidationObject child = (ValidationObject)method.invoke(object, new Object[]{});
                        unbind(child,boundMap);
                    }
                    catch (Exception e)
                    {
                        throw new RuntimeException(e);
                    }
                    continue;
                }
            }
        }
    }
}
